use std::{collections::HashMap, net::SocketAddr};

use vector_lib::{codecs::JsonSerializerConfig, configurable::configurable_component};

use super::{buffering::MessageBufferingConfig, sink::WebSocketListenerSink};
use crate::{
    codecs::EncodingConfig,
    common::http::server_auth::HttpServerAuthConfig,
    config::{AcknowledgementsConfig, Input, SinkConfig, SinkContext},
    sinks::{Healthcheck, VectorSink},
    tls::TlsEnableableConfig,
};

/// Configuration for the `websocket_server` sink.
#[configurable_component(sink(
    "websocket_server",
    "Deliver observability event data to websocket clients."
))]
#[derive(Clone, Debug)]
pub struct WebSocketListenerSinkConfig {
    /// The socket address to listen for connections on.
    ///
    /// This value _must_ include a port.
    #[configurable(metadata(docs::examples = "0.0.0.0:80"))]
    #[configurable(metadata(docs::examples = "localhost:80"))]
    pub address: SocketAddr,

    #[configurable(derived)]
    pub tls: Option<TlsEnableableConfig>,

    #[configurable(derived)]
    pub encoding: EncodingConfig,

    #[configurable(derived)]
    #[serde(default)]
    pub subprotocol: SubProtocolConfig,

    #[configurable(derived)]
    #[serde(
        default,
        deserialize_with = "crate::serde::bool_or_struct",
        skip_serializing_if = "crate::serde::is_default"
    )]
    pub acknowledgements: AcknowledgementsConfig,

    #[configurable(derived)]
    pub message_buffering: Option<MessageBufferingConfig>,

    #[configurable(derived)]
    pub auth: Option<HttpServerAuthConfig>,

    /// Configuration of internal metrics
    #[configurable(derived)]
    #[serde(default)]
    pub internal_metrics: InternalMetricsConfig,
}

/// Configuration of internal metrics for websocket server.
#[configurable_component]
#[derive(Clone, Debug, PartialEq, Eq, Default)]
#[serde(deny_unknown_fields)]
pub struct InternalMetricsConfig {
    /// Extra tags to add to all metrics generated by this component
    #[serde(default, skip_serializing_if = "crate::serde::is_default")]
    #[configurable(metadata(
        docs::additional_props_description = "Maps extra tag keys to values."
    ))]
    pub extra_tags: HashMap<String, ExtraMetricTagsConfig>,
}

/// Configuration of extra metrics tags
#[configurable_component]
#[derive(Clone, Debug, PartialEq, Eq)]
#[serde(deny_unknown_fields, rename_all = "snake_case", tag = "type")]
#[configurable(metadata(docs::enum_tag_description = "Enum for extra metric tag values."))]
pub enum ExtraMetricTagsConfig {
    /// Hard-coded extra metric tag for all clients
    Fixed {
        /// Tag value
        value: String,
    },
    /// Extra metric tag that takes on the value of a header
    Header {
        /// Name of the header to use as value
        name: String,
    },
    /// Extra metric tag that takes on the value of a query parameter
    Query {
        /// Name of the query parameter to use as value
        name: String,
    },
    /// Extra metric tag that takes full request URL as value
    Url,
    /// Extra metric tag that uses client ip address as value
    IpAddress {
        /// Set to true if port should be included with the ip address.
        ///
        /// By default port is not included
        #[serde(default = "crate::serde::default_false")]
        with_port: bool,
    },
}

/// Configuration of websocket subprotocol handling.
#[configurable_component]
#[derive(Clone, Debug, PartialEq, Eq)]
#[serde(deny_unknown_fields, rename_all = "snake_case", tag = "type")]
#[configurable(metadata(docs::enum_tag_description = "Enum for websocket subprotocol handling."))]
pub enum SubProtocolConfig {
    /// Supports any subprotocol that the client sends. First of the requested subprotocols will be accepted.
    Any,
    /// Supports only listed subprotocols. If client doesn't send any of these, server will skip
    /// `Sec-WebSocket-Protocol` header and the client can choose to close the connection then.
    Specific {
        /// List of supported `Sec-WebSocket-Protocol` values. First match out of requested
        /// subprotocols will be accepted.
        supported_subprotocols: Vec<String>,
    },
}

impl Default for SubProtocolConfig {
    fn default() -> Self {
        Self::Specific {
            supported_subprotocols: Vec::default(),
        }
    }
}

impl Default for WebSocketListenerSinkConfig {
    fn default() -> Self {
        Self {
            address: "0.0.0.0:8080".parse().unwrap(),
            encoding: JsonSerializerConfig::default().into(),
            tls: None,
            acknowledgements: Default::default(),
            message_buffering: None,
            subprotocol: Default::default(),
            auth: None,
            internal_metrics: InternalMetricsConfig::default(),
        }
    }
}

#[async_trait::async_trait]
#[typetag::serde(name = "websocket_server")]
impl SinkConfig for WebSocketListenerSinkConfig {
    async fn build(&self, cx: SinkContext) -> crate::Result<(VectorSink, Healthcheck)> {
        let ws_sink = WebSocketListenerSink::new(self.clone(), cx)?;

        Ok((
            VectorSink::from_event_streamsink(ws_sink),
            Box::pin(async move { Ok(()) }),
        ))
    }

    fn input(&self) -> Input {
        Input::new(self.encoding.config().input_type())
    }

    fn acknowledgements(&self) -> &AcknowledgementsConfig {
        &self.acknowledgements
    }
}

impl_generate_config_from_default!(WebSocketListenerSinkConfig);

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn generate_config() {
        crate::test_util::test_generate_config::<WebSocketListenerSinkConfig>();
    }
}
